package com.pfzy.lib.sdk.utils.zip;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * 随机访问文件数据源
 */
class RandomAccessFileDataSource implements DataSource {
    private static final int MAX_READ_CHUNK_SIZE = 1048576;
    private final FileChannel mChannel;
    private final long mOffset;
    private final long mSize;

    public RandomAccessFileDataSource(RandomAccessFile file) {
        this.mChannel = file.getChannel();
        this.mOffset = 0L;
        this.mSize = -1L;
    }

    public RandomAccessFileDataSource(RandomAccessFile file, long offset, long size) {
        this(file.getChannel(), offset, size);
    }

    private RandomAccessFileDataSource(FileChannel channel, long offset, long size) {
        if (offset < 0) {
            throw new IndexOutOfBoundsException("offset: " + size);
        }
        if (size < 0) {
            throw new IndexOutOfBoundsException("size: " + size);
        }
        this.mChannel = channel;
        this.mOffset = offset;
        this.mSize = size;
    }

    /**
     * 文件大小
     * @return
     */
    @Override
    public long size() {
        if (this.mSize == -1) {
            try {
                return this.mChannel.size();
            } catch (IOException e) {
                return 0L;
            }
        }
        return this.mSize;
    }

    @Override
    public RandomAccessFileDataSource slice(long offset, long size) {
        long sourceSize = size();
        checkChunkValid(offset, size, sourceSize);
        return (offset == 0 && size == sourceSize) ? this : new RandomAccessFileDataSource(this.mChannel, this.mOffset + offset, size);
    }

    /**
     * 文件片
     * @param offset
     * @param size
     * @return
     */
    @Override
    public void feed(long offset, long size, DataSink sink) throws IOException {
        long sourceSize = size();
        checkChunkValid(offset, size, sourceSize);
        if (size != 0) {
            long chunkOffsetInFile = this.mOffset + offset;
            long remaining = size;
            // 分配新的直接字节缓冲区。
            ByteBuffer buf = ByteBuffer.allocateDirect((int) Math.min(remaining, 1048576L));
            while (remaining > 0) {
                // 返回两个长值中的较小值。  capacity 返回此缓冲区的容量。
                int chunkSize = (int) Math.min(remaining, buf.capacity());
                // 设置此缓冲区的限制。
                int chunkRemaining = chunkSize;
                // 用于读取、写入、映射和操作文件的通道。
                buf.limit(chunkSize);
                synchronized (this.mChannel) {
                    this.mChannel.position(chunkOffsetInFile);
                    while (chunkRemaining > 0) {
                        int read = this.mChannel.read(buf);
                        if (read < 0) {
                            throw new IOException("Unexpected EOF encountered");
                        }
                        chunkRemaining -= read;
                    }
                }
                buf.flip();
                sink.consume(buf);
                buf.clear();
                chunkOffsetInFile += chunkSize;
                remaining -= chunkSize;
            }
        }
    }

    @Override
    public void copyTo(long offset, int size, ByteBuffer dest) throws IOException {
        int chunkSize;
        long sourceSize = size();
        checkChunkValid(offset, size, sourceSize);
        if (size != 0) {
            if (size > dest.remaining()) {
                throw new BufferOverflowException();
            }
            long offsetInFile = this.mOffset + offset;
            int remaining = size;
            int prevLimit = dest.limit();
            try {
                dest.limit(dest.position() + size);
                while (remaining > 0) {
                    synchronized (this.mChannel) {
                        this.mChannel.position(offsetInFile);
                        chunkSize = this.mChannel.read(dest);
                    }
                    offsetInFile += chunkSize;
                    remaining -= chunkSize;
                }
            } finally {
                dest.limit(prevLimit);
            }
        }
    }

    /**
     * 获取缓冲区数据
     * @param offset
     * @param size
     * @return
     * @throws IOException
     */
    @Override
    public ByteBuffer getByteBuffer(long offset, int size) throws IOException {
        if (size < 0) {
            throw new IndexOutOfBoundsException("size: " + size);
        }
        ByteBuffer result = ByteBuffer.allocate(size);
        copyTo(offset, size, result);
        result.flip();
        return result;
    }

    /**
     * 检查区块有效
     * @param offset
     * @param size
     * @param sourceSize
     */
    private static void checkChunkValid(long offset, long size, long sourceSize) {
        if (offset < 0) {
            throw new IndexOutOfBoundsException("offset: " + offset);
        }
        if (size < 0) {
            throw new IndexOutOfBoundsException("size: " + size);
        }
        if (offset > sourceSize) {
            throw new IndexOutOfBoundsException("offset (" + offset + ") > source size (" + sourceSize + ")");
        }
        long endOffset = offset + size;
        if (endOffset < offset) {
            throw new IndexOutOfBoundsException("offset (" + offset + ") + size (" + size + ") overflow");
        }
        if (endOffset > sourceSize) {
            throw new IndexOutOfBoundsException("offset (" + offset + ") + size (" + size + ") > source size (" + sourceSize + ")");
        }
    }
}
